/*The MIT License

Copyright (c) <1/27/2011> <erick.difiore>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

package com.d4a5.mapping.core
{
	import flash.geom.Point;
	import flash.net.ObjectEncoding;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import mx.utils.StringUtil;
	
	public class AmfFactory
	{
		//public static const formatShapefile:int = 0;
		public static const formatESRI:int = 1;
		public static const formatGoogle:int = 2;
		public static const formatGoogle3D:int = 3;
		public static const formatESRIJSON:int = 4;
		
		
		public static const typeNULL:int = 0;
		
		public static const typePoint:int = 1;
		public static const typePolyline:int = 3;
		public static const typePolygon:int = 5;
		public static const typeMultipoint:int = 8;
		
		public static const typePointZ:int = 11;
		public static const typePolylineZ:int = 13;
		public static const typePolygonZ:int = 15;
		public static const typeMultipointZ:int = 18;
		
		public static const typePointM:int = 21;
		public static const typePolylineM:int = 23;
		public static const typePolygonM:int = 25;
		public static const typeMultipointM:int = 28;
		
		public static const typeMultipatch:int = 31;
		
		
		
		
		public static function Create( inputFormat:int , geometryType:int , geometry:Object , attributes:Object , storeAsFloat:Boolean = false , dataIsInGeographic:Boolean = false ) : AmfObject {
			var output:AmfObject;
			
			switch( inputFormat ){
				case AmfFactory.formatESRI :
					output = AmfFactory.createESRI( geometry , geometryType , attributes , storeAsFloat , dataIsInGeographic );
					break;
				case AmfFactory.formatGoogle :
					output = AmfFactory.createGoogle( geometry , geometryType , attributes , storeAsFloat , dataIsInGeographic );
					break;
				case AmfFactory.formatGoogle3D :
					output = AmfFactory.createGooglePoint3d( geometry , geometryType , attributes , storeAsFloat , dataIsInGeographic );
					break;
				case AmfFactory.formatESRIJSON :
					output = AmfFactory.createESRIJSON( geometry , geometryType , attributes , storeAsFloat , dataIsInGeographic );
					break;
				default :
					throw new Error( "Invalid Geometry Format: " + inputFormat.toString() );
					break;
			}
			return output;
		}
		
		protected static function ReForm( amf:Object ) : AmfObject {
			var x:MinMax = new MinMax( amf.bounds.x.min ,  amf.bounds.x.max );
			var y:MinMax = new MinMax( amf.bounds.y.min ,  amf.bounds.y.max );
			var z:MinMax = new MinMax( amf.bounds.z.min ,  amf.bounds.z.max );
			var m:MinMax = new MinMax( amf.bounds.m.min ,  amf.bounds.m.max );
			return new AmfObject( amf.geometry , amf.geometryType , new Bounds( x , y , z , m ), amf.ringsCount , amf.dimensions , amf.vertexData , amf.vertexDataType , amf.attributes , amf.storedAsFloat , amf.idNum );
		}
		
		public static function Package( aps:AmfPackages ) : ByteArray {
			var bytes:ByteArray;
			
			var outputObjects:Object = new Object();
			var length:int = aps.length;
			for( var i:int = 0; i < length; i++ ){
				
				var ap:AmfPackage = aps.getPackage( i );
				if( ap ){
					
					bytes = new ByteArray();
					bytes.endian = Endian.BIG_ENDIAN;
					
					if( ap.type == AmfPackage.typeObjectData ){
						bytes.writeObject( ap.data );
					}
					else if( ap.type == AmfPackage.typeAmfObjectData ){
						
						var arr:Array = ap.data as Array;
						var arrLength:int = arr.length;
						for( var j:int = 0; j < arrLength; j++ ){
							
							var item:Object = arr[j];
							if( item is AmfObject ){
								bytes.writeObject( item );
							}
							else if( item is IAmfObject ){
								bytes.writeObject( item.amf );
							}
						}
						
					}
				}
				outputObjects[ap.type.toString() +  ap.name] = bytes;
			}
			
			bytes = new ByteArray();
			bytes.objectEncoding = ObjectEncoding.AMF3;
			bytes.endian = Endian.BIG_ENDIAN;
			bytes.writeObject( outputObjects );
			bytes.compress();
			return bytes;
		}
		
		public static function UnPackage( bytes:ByteArray ) : AmfPackages {
			try{
				if( bytes == null ){
					throw new Error( "UnPackage: bytes argument is null" );
				}
				
				bytes.endian = Endian.BIG_ENDIAN;
				bytes.uncompress();
				bytes.position = 0;
				bytes.objectEncoding = ObjectEncoding.AMF3;
				
				var aps:AmfPackages = new AmfPackages();
				var objects:Object = bytes.readObject();
				for( var key:String in objects ){
					var ts:String = key.substr( 0 , 1 );
					var type:int = int( Number( ts ) );
					var name:String = key.substr(1);
					if( type == AmfPackage.typeObjectData ){
						aps.add( new AmfPackage( name , objects[key].readObject() , type ) );
					}
					else if( type == AmfPackage.typeAmfObjectData ){
						var arrBytes:ByteArray = objects[key] as ByteArray;
						var outArr:Array = new Array();
						while( arrBytes.bytesAvailable > 0 ){
							outArr.push( AmfFactory.ReForm( arrBytes.readObject() ) );
						}
						aps.add( new AmfPackage( name , outArr , type ) );
					}
				}
			}
			catch( error:Error ){
				throw new Error( "Use Flash Player 10" );
			}
			
			return aps;
		}
		
		
		private static function createGoogle( geom:Object , geomType:int , attributes:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean = false ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			var x:Number;
			var y:Number;
			var numRings:int;
			
			if( geomType == AmfObject.typePoint ){
				Utils.GeographicToWebMercatorFaster( geom.lat() , geom.lng() , webMercatorPt );
				x = webMercatorPt.x;
				y = webMercatorPt.y;
				write( x );
				write( y );
				bounds.unionPt( x , y );
				numRings = 1;
			}
			else if( geomType == AmfObject.typePolygon || geomType == AmfObject.typePolyline ){
				var xmin:Number = Number.POSITIVE_INFINITY;
				var ymin:Number = Number.POSITIVE_INFINITY;
				var xmax:Number = Number.NEGATIVE_INFINITY;
				var ymax:Number = Number.NEGATIVE_INFINITY;
				
				var webMercatorPt:Point = new Point();
				
				numRings = geom.length;
				for( var i:int = 0; i < numRings; i++ ){
					var points:Array = geom[i] as Array;
					
					var numPoints:int = points.length;
					b.writeInt( numPoints );
					
					for( var j:int = 0; j < numPoints; j++ ){
						var pt:Object = points[j];
						
						Utils.GeographicToWebMercatorFaster( pt.lat() , pt.lng() , webMercatorPt );
						x = webMercatorPt.x;
						y = webMercatorPt.y;
						
						write( x );
						write( y );
						xmin = ( x < xmin ) ? x : xmin;
						ymin = ( y < ymin ) ? y : ymin;
						xmax = ( x > xmax ) ? x : xmax;
						ymax = ( y > ymax ) ? y : ymax;
					}
				}
				bounds = new Bounds( new MinMax( xmin , xmax ) , new MinMax( ymin , ymax ) );
			}
			
			return new AmfObject( b , geomType , bounds , numRings , AmfObject.dimensions2 , null , 0 , attributes , storeAsFloat );
		}
		
		
		private static function createGooglePoint3d( geom:Object , geomType:int , attributes:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean = false ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			var x:Number;
			var y:Number;
			var z:Number;
			
			x = geom.x;
			y = geom.y;
			z = geom.z;
			write( x );
			write( y );
			write( z );
			bounds.unionPt( x , y , z );
			
			return new AmfObject( b , AmfObject.typePoint , bounds , 1 , AmfObject.dimensions3 , null , 0 , attributes , storeAsFloat   );
		}
		
		private static function createESRI( geom:Object , geomType:int , attributes:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean = false ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			var x:Number;
			var y:Number;
			var numRings:int;
			var wmPt:Point = new Point( 0 , 0 );
			
			if( geomType == AmfObject.typePoint ){
				x = geom.x;
				y = geom.y;
				
				if( dataIsInGeographic ){
					Utils.GeographicToWebMercatorFaster( y , x , wmPt );
					x = wmPt.x;
					y = wmPt.y;
				}
				
				write( x );
				write( y );
				bounds.unionPt( x , y );
				numRings = 1;
			}
			else if( geomType == AmfObject.typePolygon || geomType == AmfObject.typePolyline ){
				var xmin:Number = Number.POSITIVE_INFINITY;
				var ymin:Number = Number.POSITIVE_INFINITY;
				var xmax:Number = Number.NEGATIVE_INFINITY;
				var ymax:Number = Number.NEGATIVE_INFINITY;
				
				numRings = geom.length;
				
				for( var i:int = 0; i < numRings; i++ ){
					var points:Array = geom[i] as Array;
					
					var numPoints:int = points.length;
					b.writeInt( numPoints );
					
					for( var j:int = 0; j < numPoints; j++ ){
						var pt:Object = points[j];
						x = pt.x;
						y = pt.y;
						
						if( dataIsInGeographic ){
							Utils.GeographicToWebMercatorFaster( y , x , wmPt );
							x = wmPt.x;
							y = wmPt.y;
						}
						
						write( x );
						write( y );
						xmin = ( x < xmin ) ? x : xmin;
						ymin = ( y < ymin ) ? y : ymin;
						xmax = ( x > xmax ) ? x : xmax;
						ymax = ( y > ymax ) ? y : ymax;
					}
				}
				bounds = new Bounds( new MinMax( xmin , xmax ) , new MinMax( ymin , ymax ) );
			}
			
			return new AmfObject( b , geomType , bounds , numRings , AmfObject.dimensions2 , null , 0 , attributes , storeAsFloat  );
		}
		
		private static function createESRIJSON( geom:Object , geomType:int , attributes:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean = false ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			var x:Number;
			var y:Number;
			var numRings:int;
			var wmPt:Point = new Point( 0 , 0 );
			
			if( geomType == AmfObject.typePoint ){
				x = geom[0];
				y = geom[1];
				
				if( dataIsInGeographic ){
					Utils.GeographicToWebMercatorFaster( y , x , wmPt );
					x = wmPt.x;
					y = wmPt.y;
				}
				
				write( x );
				write( y );
				bounds.unionPt( x , y );
				numRings = 1;
			}
			else if( geomType == AmfObject.typePolygon || geomType == AmfObject.typePolyline ){
				var xmin:Number = Number.POSITIVE_INFINITY;
				var ymin:Number = Number.POSITIVE_INFINITY;
				var xmax:Number = Number.NEGATIVE_INFINITY;
				var ymax:Number = Number.NEGATIVE_INFINITY;
				
				numRings = geom.length;
				
				
				for( var i:int = 0; i < numRings; i++ ){
					var points:Array = geom[i] as Array;
					
					var numPoints:int = points.length;
					b.writeInt( numPoints );
					
					
					
					for( var j:int = 0; j < numPoints; j++ ){
						var pt:Array = points[j];
						x = pt[0];
						y = pt[1];
						
						if( dataIsInGeographic ){
							Utils.GeographicToWebMercatorFaster( y , x , wmPt );
							x = wmPt.x;
							y = wmPt.y;
						}
						
						write( x );
						write( y );
						xmin = ( x < xmin ) ? x : xmin;
						ymin = ( y < ymin ) ? y : ymin;
						xmax = ( x > xmax ) ? x : xmax;
						ymax = ( y > ymax ) ? y : ymax;
					}
				}
				bounds = new Bounds( new MinMax( xmin , xmax ) , new MinMax( ymin , ymax ) );
			}
			
			return new AmfObject( b , geomType , bounds , numRings , AmfObject.dimensions2 , null , 0 , attributes , storeAsFloat  );
		}
		
		
		
		public static function CreateFromShapefile( shpBytes:ByteArray , dbfBytes:ByteArray = null, storeAsFloat:Boolean = true , dataIsInGeographic:Boolean = false ) : Array {
			var array:Array = new Array();
			
			shpBytes.position = 0;
			shpBytes.endian = Endian.BIG_ENDIAN;
			if( shpBytes.readInt() != 0x0000270A ){
				throw( new Error( "Error: Bad Magic in Shapefile Header" ) );
			}
			shpBytes.endian = Endian.LITTLE_ENDIAN;
			shpBytes.position = 32;
			var geomType:int = shpBytes.readInt();
			shpBytes.position = 100;
			
			var funcName:String = "createFromShapefile" + geomType.toString();
			
			if( AmfFactory[funcName] is Function ){
				while( shpBytes.bytesAvailable ){
					shpBytes.endian = Endian.BIG_ENDIAN;
					var id:int = shpBytes.readInt();
					shpBytes.position += 8;
					array.push( AmfFactory[funcName]( id , shpBytes , geomType , storeAsFloat , dataIsInGeographic ) );
				}
			}
			
			if( dbfBytes ){
				AmfFactory.AttachDbfFile( dbfBytes , array );
			}
			
			return array;
		}
		
		
		
		private static function createFromShapefile0( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			return null;
		}
		
		private static function createFromShapefile1( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			
			geom.endian = Endian.LITTLE_ENDIAN;
			geom.position = 4;
			
			var x:Number = geom.readDouble();
			var y:Number = geom.readDouble();
			
			var wmPt:Point = new Point( x , y );
			if( dataIsInGeographic ){
				Utils.GeographicToWebMercatorFaster( y , x , wmPt );
			}
			
			write( wmPt.x );
			write( wmPt.y );
			bounds.unionPt( wmPt.x , wmPt.y );
			
			return new AmfObject( b , AmfObject.typePoint , bounds , 1 , AmfObject.dimensions2 , null , AmfObject.vertexDataNone , null , storeAsFloat ,id );
		}
		
		private static function createFromShapefile3( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePoly( geom , storeAsFloat , dataIsInGeographic );
			
			return new AmfObject( obj.bytes , AmfObject.typePolyline , obj.bounds, obj.numRings , AmfObject.dimensions2 , null , AmfObject.vertexDataNone ,  null , storeAsFloat ,id );
		}
		
		private static function createFromShapefile5( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePoly( geom , storeAsFloat , dataIsInGeographic );
			
			return new AmfObject( obj.bytes , AmfObject.typePolygon , obj.bounds, obj.numRings , AmfObject.dimensions2 , null , AmfObject.vertexDataNone ,  null , storeAsFloat ,id );
		}
		
		private static function createFromShapefile8( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefileMultiPoint( geom , storeAsFloat , dataIsInGeographic );
			
			return new AmfObject( obj.bytes , AmfObject.typeMultipoint , obj.bounds, obj.numRings , AmfObject.dimensions2 , null , AmfObject.vertexDataNone ,  null , storeAsFloat ,id );
		}
		
		
		
		
		private static function createFromShapefile11( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			
			geom.endian = Endian.LITTLE_ENDIAN;
			geom.position = 4;
			
			var x:Number = geom.readDouble();
			var y:Number = geom.readDouble();
			var z:Number = geom.readDouble();
			var m:Number = geom.readDouble();
			
			var wmPt:Point = new Point( x , y );
			if( dataIsInGeographic ){
				Utils.GeographicToWebMercatorFaster( y , x , wmPt );
			}
			
			write( wmPt.x );
			write( wmPt.y );
			write( z );
			
			var v:ByteArray = new ByteArray();
			v.endian = Endian.BIG_ENDIAN;
			v.writeDouble( m );
			bounds.unionPt( wmPt.x , wmPt.y , z , m );
			
			return new AmfObject( b , AmfObject.typePoint , bounds , 1 , AmfObject.dimensions3 , v , AmfObject.vertexDataNumber ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile13( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePolyZ( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			return new AmfObject( obj.bytes , AmfObject.typePolyline , obj.bounds, obj.numRings , AmfObject.dimensions3 , vObj.bytes , 0 ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile15( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePolyZ( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			return new AmfObject( obj.bytes , AmfObject.typePolygon , obj.bounds, obj.numRings , AmfObject.dimensions3 , vObj.bytes , 0 ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile18( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefileMultiPointZ( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			return new AmfObject( obj.bytes , AmfObject.typeMultipoint , obj.bounds, obj.numRings , AmfObject.dimensions3 , vObj.bytes , 0 ,  null , storeAsFloat ,id );
		}
		
		private static function createFromShapefile21( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			var bounds:Bounds = new Bounds();
			
			geom.endian = Endian.LITTLE_ENDIAN;
			geom.position = 4;
			
			var x:Number = geom.readDouble();
			var y:Number = geom.readDouble();
			var m:Number = geom.readDouble();
			
			var wmPt:Point = new Point( x , y );
			if( dataIsInGeographic ){
				Utils.GeographicToWebMercatorFaster( y , x , wmPt );
			}
			
			write( wmPt.x );
			write( wmPt.y );
			
			var v:ByteArray = new ByteArray();
			v.endian = Endian.BIG_ENDIAN;
			v.writeDouble( m );
			
			bounds.unionPt( wmPt.x , wmPt.y , Number.NaN , m );
			
			
			return new AmfObject( b , AmfObject.typePoint , bounds , 1 , AmfObject.dimensions2 , v , AmfObject.vertexDataNumber ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile23( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePoly( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			
			return new AmfObject( obj.bytes , AmfObject.typePolyline , obj.bounds , obj.numRings , AmfObject.dimensions2 , vObj.bytes , 0 ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile25( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefilePoly( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			return new AmfObject( obj.bytes , AmfObject.typePolygon , obj.bounds, obj.numRings , AmfObject.dimensions2 , vObj.bytes , AmfObject.vertexDataNumber ,  null , storeAsFloat ,id );
		}
		private static function createFromShapefile28( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			var obj:Object = AmfFactory.createFromShapefileMultiPoint( geom , storeAsFloat , dataIsInGeographic );
			var vObj:Object = AmfFactory.createFromShapefileMData( geom );
			obj.bounds.m.union( vObj.minmax );
			
			return new AmfObject( obj.bytes , AmfObject.typeMultipoint , obj.bounds, obj.numRings , AmfObject.dimensions2 , vObj.bytes , 0 , null , storeAsFloat ,id );
		}
		
		private static function createFromShapefile31( id:int , geom:Object , geomType:int , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : AmfObject {
			return null;
		}
		
		
		private static function createFromShapefileMData( geom:Object ) : Object { 
			var v:ByteArray = new ByteArray();
			v.endian = Endian.BIG_ENDIAN;
			
			var obj:Object = new Object();
			obj.minmax = new MinMax( geom.readDouble() , geom.readDouble() );
			
			while( geom.bytesAvailable ){
				v.writeDouble( geom.readDouble() );
			}
			obj.bytes = v;
			
			return obj;
		}
		
		private static function createFromShapefileMultiPoint( geom:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : Object {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			
			geom.endian = Endian.LITTLE_ENDIAN;
			
			var obj:Object = new Object();
			var xmin:Number = geom.readDouble();
			var ymin:Number = geom.readDouble();
			var xmax:Number = geom.readDouble();
			var ymax:Number = geom.readDouble();
			
			
			if( dataIsInGeographic ){
				var ptMin:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymin , xmin , ptMin );
				var ptMax:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymax , xmax , ptMax );
				
				obj.bounds = new Bounds( new MinMax( ptMin.x , ptMax.x ), new MinMax( ptMin.y , ptMax.y ) );
			}
			else{
				obj.bounds = new Bounds( new MinMax( xmin , xmax ), new MinMax( ymin , ymax ) );
			}
			
			
			
			var numPoints:int = geom.readInt();
			b.writeInt( numPoints );
			
			
			if( dataIsInGeographic ){
				var x:Number;
				var y:Number;
				var wmPt:Point = new Point();
				
				for( var k:int = 0; k < numPoints; k++ ){
					x = geom.readDouble();
					y = geom.readDouble();
					Utils.GeographicToWebMercatorFaster( y , x , wmPt );
					write( wmPt.x );
					write( wmPt.y );	
				}
			}
			else{
				for( var j:int = 0; j < numPoints; j++ ){
					write( geom.readDouble() );
					write( geom.readDouble() );	
				}
			}
			
			obj.bytes = b;
			obj.numRings = 1;
			return obj;
		}
		
		private static function createFromShapefileMultiPointZ( geom:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : Object {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			
			geom.endian = Endian.LITTLE_ENDIAN;
			
			var obj:Object = new Object();
			var xmin:Number = geom.readDouble();
			var ymin:Number = geom.readDouble();
			var xmax:Number = geom.readDouble();
			var ymax:Number = geom.readDouble();
			
			
			if( dataIsInGeographic ){
				var ptMin:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymin , xmin , ptMin );
				var ptMax:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymax , xmax , ptMax );
				
				obj.bounds = new Bounds( new MinMax( ptMin.x , ptMax.x ), new MinMax( ptMin.y , ptMax.y ) );
			}
			else{
				obj.bounds = new Bounds( new MinMax( xmin , xmax ), new MinMax( ymin , ymax ) );
			}
			
			
			var numPoints:int = geom.readInt();
			
			var xOffset:int = geom.position;
			geom.position = xOffset + ( 16 * numPoints );
			
			obj.bounds.z.union( new MinMax( geom.readDouble() , geom.readDouble() ) );
			
			geom.position = xOffset;
			b.writeInt( numPoints );
			
			
			var zOffset:int = xOffset + ( 16 * numPoints ) + 16;
			var ptOffset:int = 0;
			
			if( dataIsInGeographic ){
				var x:Number;
				var y:Number;
				var wmPt:Point = new Point();
				
				for( var k:int = 0; k < numPoints; k++ , ptOffset+=8 ){
					geom.position = xOffset + ptOffset + ptOffset;
					
					x = geom.readDouble();
					y = geom.readDouble();
					Utils.GeographicToWebMercatorFaster( y , x , wmPt );
					write( wmPt.x );
					write( wmPt.y );
					
					geom.position = zOffset + ptOffset;
					write( geom.readDouble() );	
				}
			}
			else{
				for( var j:int = 0; j < numPoints; j++ , ptOffset+=8 ){
					geom.position = xOffset + ptOffset + ptOffset;
					write( geom.readDouble() );
					write( geom.readDouble() );	
					geom.position = zOffset + ptOffset;
					write( geom.readDouble() );	
				}
			}
			
			obj.bytes = b;
			obj.numRings = 1;
			return obj;
		}
		
		private static function createFromShapefilePoly( geom:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : Object {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			
			geom.endian = Endian.LITTLE_ENDIAN;
			
			var obj:Object = new Object();
			var xmin:Number = geom.readDouble();
			var ymin:Number = geom.readDouble();
			var xmax:Number = geom.readDouble();
			var ymax:Number = geom.readDouble();
			
			
			if( dataIsInGeographic ){
				var ptMin:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymin , xmin , ptMin );
				var ptMax:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymax , xmax , ptMax );
				
				obj.bounds = new Bounds( new MinMax( ptMin.x , ptMax.x ), new MinMax( ptMin.y , ptMax.y ) );
			}
			else{
				obj.bounds = new Bounds( new MinMax( xmin , xmax ), new MinMax( ymin , ymax ) );
			}
			
			
			var numParts:int = geom.readInt();
			var numPoints:int = geom.readInt();
			var parts:Array = new Array();
			for( var i:int = 0; i < numParts; i++ ){
				parts.push( geom.readInt() );
			}
			parts.push( numPoints );
			
			
			for( var k:int = 0; k < numParts; k++ ){
				var start:int = parts[k];
				var stop:int = parts[k+1];
				b.writeInt( stop - start );
				
				if( dataIsInGeographic ){
					var x:Number;
					var y:Number;
					var wmPt:Point = new Point();
					
					for( var m:int = start; m < stop; m++ ){
						x = geom.readDouble();
						y = geom.readDouble();
						Utils.GeographicToWebMercatorFaster( y , x , wmPt );
						write( wmPt.x );
						write( wmPt.y );
					}
				}
				else{
					for( var j:int = start; j < stop; j++ ){
						write( geom.readDouble() );
						write( geom.readDouble() );
					}
				}
			}
			obj.bytes = b;
			obj.numRings = numParts;
			return obj;
		}
		
		private static function createFromShapefilePolyZ( geom:Object , storeAsFloat:Boolean , dataIsInGeographic:Boolean ) : Object {
			var b:ByteArray = new ByteArray();
			b.endian = Endian.BIG_ENDIAN;
			var write:Function = ( storeAsFloat ) ? b.writeFloat : b.writeDouble;
			
			geom.endian = Endian.LITTLE_ENDIAN;
			
			var obj:Object = new Object();
			var xmin:Number = geom.readDouble();
			var ymin:Number = geom.readDouble();
			var xmax:Number = geom.readDouble();
			var ymax:Number = geom.readDouble();
			
			if( dataIsInGeographic ){
				var ptMin:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymin , xmin , ptMin );
				var ptMax:Point = new Point();
				Utils.GeographicToWebMercatorFaster( ymax , xmax , ptMax );
				
				obj.bounds = new Bounds( new MinMax( ptMin.x , ptMax.x ), new MinMax( ptMin.y , ptMax.y ) );
			}
			else{
				obj.bounds = new Bounds( new MinMax( xmin , xmax ), new MinMax( ymin , ymax ) );
			}
			
			var numParts:int = geom.readInt();
			var numPoints:int = geom.readInt();
			var parts:Array = new Array();
			for( var i:int = 0; i < numParts; i++ ){
				parts.push( geom.readInt() );
			}
			parts.push( numPoints );
			
			var xOffset:int = geom.position;
			geom.position = xOffset + ( 16 * numPoints );
			
			obj.bounds.z.union( new MinMax( geom.readDouble() , geom.readDouble() ) );
			
			geom.position = xOffset;
			b.writeInt( numPoints );
			
			var zOffset:int = xOffset + ( 16 * numPoints ) + 16;
			var ptOffset:int = 0;
			
			for( var k:int = 0; k < numParts; k++ ){
				var start:int = parts[k];
				var stop:int = parts[k+1];
				b.writeInt( stop - start );
				
				for( var j:int = start; j < stop; j++ , ptOffset+=8 ){
					geom.position = xOffset + ptOffset + ptOffset;
					
					if( dataIsInGeographic ){
						var x:Number;
						var y:Number;
						var wmPt:Point = new Point();
						
						x = geom.readDouble();
						y = geom.readDouble();
						Utils.GeographicToWebMercatorFaster( y , x , wmPt );
						write( wmPt.x );
						write( wmPt.y );
					}
					else{
						write( geom.readDouble() );
						write( geom.readDouble() );	
					}
					geom.position = zOffset + ptOffset;
					write( geom.readDouble() );	
				}
			}
			
			obj.bytes = b;
			obj.numRings = numParts;
			return obj;
		}
		
		public static function AttachTextFile( data:String , amfs:Array , textfileMatchField:String , amfsMatchField:String , delimiter:* ) : void {
			
			//create a hash map of the amfs data so that we can just do a lookup
			//as opposed to looping through all the data, over and over and over again
			var amf:AmfObject;
			var matchMap:Object = new Object();
			var length:int = amfs.length;
			for( var a:int = 0; a < length; a++ ){
				amf = amfs[a] as AmfObject;
				if( amf.attributes[ amfsMatchField ] && matchMap[ amf.attributes[ amfsMatchField ] ] == null ){
					matchMap[ amf.attributes[ amfsMatchField ] ] = amf;
				}
				else{
					throw new Error( "AttachTextFile: Amf's matchfield(" + amfsMatchField + ") contains non-unique data(" + amf.attributes[ amfsMatchField ].toString() + ")" );
				}
			}
			
			//split data into lines
			var lines:Array = data.split( /\r\n|\n/ );
			
			
			//get headers and the match fields index
			var fields:Array = (lines[0] as String).split( delimiter );
			var fieldCount:int = fields.length;
			var textfileMatchFieldIndex:int = -1;
			for( var f:int = 0; f < fieldCount; f++ ){
				if( fields[f] == textfileMatchField ){
					textfileMatchFieldIndex = f;
					break;
				}
			}
			
			
			//match fields and add data
			if( textfileMatchFieldIndex == -1 ){
				throw new Error( "AttachTextFile: Could not find Textfile's matchfield(" + textfileMatchField + ")" );
			}
			else{
				length = lines.length;
				for( var i:int = 1; i < length; i++ ){
					
					var elements:Array = (lines[i] as String).split( delimiter );
					var match:String = elements[textfileMatchFieldIndex];
					amf = matchMap[match];
					if( amf ){
						for( var j:int = 0; j < fieldCount; j++ ){
							var field:String = fields[j];
							
							//this is so we do not loose data
							if( amf.attributes[ field ] == null ){
								amf.attributes[ field ] = elements[j];	
							}
							else if( amf.attributes[ field ] is Array ){
								amf.attributes[ field ].push( elements[j] );
							}
							else{
								var temp:Object = amf.attributes[ field ];
								amf.attributes[ field ] = new Array( temp , elements[j] );
							}
						}
					}
					else{
						trace( "AttachTextFile: Could not find match for line " + i.toString() );
					}
				}
			}
			
			
		}
		
		
		public static function AttachDbfFile( dbfBytes:ByteArray , amfs:Array ) : void {
			var amfsCount:int = 0;
			var length:int = amfs.length;
			var amfMap:Array = new Array();
			var amf:AmfObject;
			
			while( length-- ){
				var obj:Object = amfs[length];
				
				amf = ( obj is AmfObject ) ? (obj as AmfObject ) :
					(( (obj is IAmfObject) ) ? (obj as IAmfObject).amf :
						(( obj.amf ) ? obj.amf : null) );
				
				if( amf ){
					if( amfMap[amf.idNum] ){
						throw new Error( "AttachDbfFile: Id(" + amf.idNum + ") is not unique." );
					}
					amfMap[amf.idNum] = amf;
					amfsCount++;
				}
			}
			
			
			var dbfRecs:Array = AmfFactory.ReadDbfFile( dbfBytes );
			length = dbfRecs.length;
			for( var i:int = 1; i < length; i++ ){
				amf = amfMap[i];
				if( amf ){
					amf.attributes = dbfRecs[i];
				}
			}
			
		}
		
		public static function ReadDbfFile( dbfBytes:ByteArray ) : Array {
			var records:Array;
			
			if( dbfBytes.length > 8 ){
				dbfBytes.position = 0;
				dbfBytes.endian = Endian.LITTLE_ENDIAN;
				
				dbfBytes.position = 4;
				var numRecs:int = dbfBytes.readInt();
				if( numRecs > 0 ){
					
					var headerLength:int = dbfBytes.readShort();
					var recordsLength:int = dbfBytes.readShort();
					headerLength--;
					dbfBytes.position += 20;
					
					
					//read dbf fields 
					var allFieldsLength:int = 0;
					var fields:Array = new Array();
					while( dbfBytes.position < headerLength ){
						fields.push( new DbfDataField(dbfBytes) );
						allFieldsLength += fields[fields.length-1].length;
					}
					dbfBytes.position += 1;
					
					
					records = new Array( numRecs + 1 );
					
					//read dbf records
					var numFields:int = fields.length;
					for( var j:int = 1; j <= numRecs; j++ ){
						var recordDeletedFlag:int = dbfBytes.readByte();
						
						var record:Object = new Object();
						for( var i:int = 0; i < numFields; i++ ){
							var field:DbfDataField = fields[i] as DbfDataField;
							record[field.name] = field.reader( dbfBytes );
						}
						
						records[j] = record;
					}
				}
			}
			
			return records;
		}
		
		
		
	}
}

import flash.utils.ByteArray;


import mx.utils.StringUtil;


class DbfDataField{
	public var name:String;
	public var type:String;
	public var fieldDataAddress:String; 
	public var length:uint;
	public var decimalCount:uint;
	public var multiUser1:String;	
	public var workAreaId:String;
	public var flagForSetFields:String;
	public var multiUser2:String;
	public var reserved:String;
	public var indexFieldFlag:String;
	public var reader:Function;
	
	public function DbfDataField( src:ByteArray )
	{
		this.name = src.readMultiByte( 11 , "us-ascii" );
		this.type = src.readMultiByte( 1 , "us-ascii" );
		this.fieldDataAddress = src.readMultiByte( 4 , "us-ascii" );
		this.length = src.readUnsignedByte();
		this.decimalCount = src.readUnsignedByte();
		this.multiUser1 = src.readMultiByte( 2 , "us-ascii" );
		this.workAreaId = src.readMultiByte( 1 , "us-ascii" );
		this.multiUser2 = src.readMultiByte( 2 , "us-ascii" );
		this.flagForSetFields = src.readMultiByte( 1 , "us-ascii" );
		this.reserved = src.readMultiByte( 7 , "us-ascii" );
		this.indexFieldFlag = src.readMultiByte( 1 , "us-ascii" );
		
		this.reader = this[type] as Function;
	}
	
	//Character
	public function C( b:ByteArray ) : String {
		var value:String = b.readUTFBytes( this.length );
		
		//doing StringUtils.trim but faster which is neccessary
		//since this is the most common function 
		var strLength:int = value.length;
		var leftPos:int = -1;
		while( value.charAt( ++leftPos ) == " " && leftPos < strLength ){
			
		}
		
		var rightPos:int = value.length;
		while( value.charAt( --rightPos ) == " " && rightPos > leftPos ){
			
		}
		
		return value.substr( leftPos , rightPos+1 );
	}
	//Number
	public function N( b:ByteArray ) : Number {
		return Number( b.readUTFBytes( this.length ) );
	}
	//Floating point
	public function F( b:ByteArray ) : Number {
		return Number( b.readUTFBytes( this.length ) );
	}
	//Logical
	public function L( b:ByteArray ) : Boolean {
		return (b.readUTFBytes( this.length ).match( /(Y|y|T|t)/ )) as Boolean;
	}
	//Date
	public function D( b:ByteArray ) : Date {
		return new Date( b.readUTFBytes( this.length ) );
	}
	//Double
	public function O( b:ByteArray ) : Number {
		return b.readDouble();
	}
	//Integer
	public function I( b:ByteArray ) : int {
		return b.readShort();
	}
}
